Copy search matches
Several methods allow you to copy all search matches (search hits). Usually, you want to copy each line which contains text matching your search pattern, but you can also copy just the matching text. The tip starts with methods to easily list search hits, without copying them, then shows how to copy matches, or matching lines. Similar techniques, also shown below, can be used to delete all matching lines. Copying or deleting search matches is easy when simple search patterns are used. The techniques shown here also work with multiline matches. Search within a file To search, press / then type what you want to find, or press * to search for the current word. To list all lines matching your search pattern, use the following. The first command lists lines matching your last search. The second lists lines matching pattern. :g/ :g/pattern On some systems, at the "Press ENTER or type command to continue" prompt, you can select displayed text with the mouse then press Ctrl-Y to copy the selection to the clipboard. To view a window of search results, see Find in files within Vim. You can use % for the file path to search only the current file, for example: " Save file, search it for 'pattern', and open a clickable list. :w :vimgrep /pattern/ % :copen To view only the parts of a file that match your pattern, see Folding with Regular Expression. You can fold away non-matching lines, and use zr to reveal more context around the search hits. Another approach is to put the cursor on a word of interest, then type to [[Displaying a variable/macro definition|list occurrences]. Copy or delete matching lines Matching lines can be copied to the clipboard, so they can be pasted into another application. To copy all lines containing a search pattern, use the following (see here). The first command clears register a ( ). The second appends all matching lines to that register ( ). The third copies register a to the clipboard (register +) for easy pasting into another application. Replace pattern with what you want to search for, or omit it (:g//y A) to use the last search. qaq :g/pattern/y A :let @+ = @a The above procedure captures only the first line of each match. See below for copying multiline matches. It is also easy to delete lines containing single-line matches. The first of the following commands deletes all lines containing pattern, while the second deletes all lines matching the last search: :g/pattern/d :g//d Use :v//d to delete all lines that do not match the last search pattern, or :v/pattern/d to delete all lines that do not match the given pattern. If a search pattern may find multiline matches, the above procedures to delete matching lines fail. In that case, use the following which works with any kind of match. " Delete all lines in given range that contain a match, or part of a match. " :DeleteLines delete all lines matching last search " :DeleteLines pat delete all lines matching the given pattern 'pat' " The deleted lines are NOT saved anywhere. Works with multiline matches. function! DeleteLines(pattern) range let delid = '' " an id that does not occur in buffer if search(delid, 'cnw') > 0 redraw " so message is seen echo 'Error: buffer contains pattern used to delete lines' return endif let pattern = empty(a:pattern) ? @/ : a:pattern let sub = a:firstline . ',' . a:lastline . 's/' . escape(pattern, '/') " Retain newline if it is last character so do not delete following line. let rep = '/\=delid . (submatch(0) =~ "\n$" ? "\r" : "")/e' execute sub . rep . (&gdefault ? '' : 'g') execute 'g/\C' . delid . '/' . 'd' endfunction command! -nargs=? -range=% DeleteLines k'|,call DeleteLines() Copy matches The simple script shown here copies only the text that matches search hits. It works with multiline matches. With the s///n option no changes will be made to the buffer. After sourcing the following, search for a pattern, then enter :CopyMatches to copy all matches to the clipboard, or :CopyMatches x where x is any register to hold the result. function! CopyMatches(reg) let hits = [] %s//\=len(add(hits, submatch(0))) ? submatch(0) : ''/gne let reg = empty(a:reg) ? '+' : a:reg execute 'let @'.reg.' = join(hits, "\n") . "\n"' endfunction command! -register CopyMatches call CopyMatches() The above code works after any search, including searches which match text in more than one line. The expression used in the substitute replacement uses len() as a trick to call add() (which returns the list after appending a match to it). Use the following to copy matches from all buffers to register a: :let @a = '' :bufdo CopyMatches A In the following text, use search pattern \ (any word beginning with A, followed by any characters including newline, as few as possible, to a word ending with Z) to test :CopyMatches (it copies four hits: A1...1Z and A2...2Z and A3...3Z and A4...4Z): Test text bbb ccc A1 ddd eee Afake fff1Z A2 ggg2Z hhh A3 iii jjj3Z Nothing here. More A4 kkk lll4Z Copy matches or lines The script in this section provides more features. It works with multiline matches, and provides a CopyMatches command to copy only matching text, as well as a CopyLines command to copy all lines that contain a match. Each command accepts a range of lines, and can copy into any register (by default, the clipboard). The first of the following commands copies all text matching the last search into the clipboard, while the second copies all matches in lines 1 to 10 inclusive to register a, and the third copies matches from the current line (.) to the default register ("): :CopyMatches :1,10CopyMatches a :.CopyMatches " Matches can be copied into a scratch buffer instead of a register. To do that, use - as the register name. If a bang (exclamation mark) is used, the line number of each match is included. Also, a search pattern can be entered on the command line. For example, the following commands display the results for two searches in the same scratch buffer. Search matches, with line numbers, are displayed for all words beginning with 'a', and for all strings of digits. Enter the first command, then switch back to the original buffer while keeping the scratch window open, then enter the second command: :CopyMatches! - \ Create file ~/.vim/plugin/copymatches.vim (Unix) or $HOME/vimfiles/plugin/copymatches.vim (Windows) containing the script below, then restart Vim. " Plugin to copy matches (search hits which may be multiline). " Version 2012-05-03 from http://vim.wikia.com/wiki/VimTip478 " " :CopyMatches copy matches to clipboard (each match has newline added) " :CopyMatches x copy matches to register x " :CopyMatches X append matches to register x " :CopyMatches - display matches in a scratch buffer (does not copy) " :CopyMatches pat (after any of above options) use 'pat' as search pattern " :CopyMatches! (with any of above options) insert line numbers " Same options work with the :CopyLines command (which copies whole lines). " Jump to first scratch window visible in current tab, or create it. " This is useful to accumulate results from successive operations. " Global function that can be called from other scripts. function! GoScratch() let done = 0 for i in range(1, winnr('$')) execute i . 'wincmd w' if &buftype 'nofile' let done = 1 break endif endfor if !done new setlocal buftype=nofile bufhidden=hide noswapfile endif endfunction " Append match, with line number as prefix if wanted. function! s:Matcher(hits, match, linenums, subline) if !empty(a:match) let prefix = a:linenums ? printf('%3d ', a:subline) : '' call add(a:hits, prefix . a:match) endif return a:match endfunction " Append line numbers for lines in match to given list. function! s:MatchLineNums(numlist, match) let newlinecount = len(substitute(a:match, '\n\@!.', '', 'g')) if a:match =~ "\n$" let newlinecount -= 1 " do not copy next line after newline endif call extend(a:numlist, range(line('.'), line('.') + newlinecount)) return a:match endfunction " Return list of matches for given pattern in given range. " If 'wholelines' is 1, whole lines containing a match are returned. " This works with multiline matches. " Work on a copy of buffer so unforeseen problems don't change it. " Global function that can be called from other scripts. function! GetMatches(line1, line2, pattern, wholelines, linenums) let savelz = &lazyredraw set lazyredraw let lines = getline(1, line('$')) new setlocal buftype=nofile bufhidden=delete noswapfile silent put =lines 1d let hits = [] let sub = a:line1 . ',' . a:line2 . 's/' . escape(a:pattern, '/') if a:wholelines let numlist = [] " numbers of lines containing a match let rep = '/\=s:MatchLineNums(numlist, submatch(0))/e' else let rep = '/\=s:Matcher(hits, submatch(0), a:linenums, line("."))/e' endif silent execute sub . rep . (&gdefault ? '' : 'g') close if a:wholelines let last = 0 " number of last copied line, to skip duplicates for lnum in numlist if lnum > last let last = lnum let prefix = a:linenums ? printf('%3d ', lnum) : '' call add(hits, prefix . getline(lnum)) endif endfor endif let &lazyredraw = savelz return hits endfunction " Copy search matches to a register or a scratch buffer. " If 'wholelines' is 1, whole lines containing a match are returned. " Works with multiline matches. Works with a range (default is whole file). " Search pattern is given in argument, or is the last-used search pattern. function! s:CopyMatches(bang, line1, line2, args, wholelines) let l = matchlist(a:args, '^\%(\(a-zA-Z"*+-\)\%($\|\s\+\)\)\?\(.*\)') let reg = empty(l1) ? '+' : l1 let pattern = empty(l2) ? @/ : l2 let hits = GetMatches(a:line1, a:line2, pattern, a:wholelines, a:bang) let msg = 'No non-empty matches' if !empty(hits) if reg '-' call GoScratch() normal! G0m' silent put =hits " Jump to first line of hits and scroll to middle. ''+1normal! zz else execute 'let @' . reg . ' = join(hits, "\n") . "\n"' endif let msg = 'Number of matches: ' . len(hits) endif redraw " so message is seen echo msg endfunction command! -bang -nargs=? -range=% CopyMatches call s:CopyMatches(0, , , , 0) command! -bang -nargs=? -range=% CopyLines call s:CopyMatches(0, , , , 1) GetMatches() alternative The following shows an alternative method to copy matches. This script is less useful than others in this tip as it does not handle multiline matches. However, the technique may be of interest as it shows how to use the match() function. In addition, it does not involve changes to the current buffer. " Return list of matches for given pattern in given range. " This only works for matches within a single line. " Empty hits are skipped so search for '\d*\ze,' is not stuck in '123,456'. " If omit match() 'count' argument, pattern '^.' matches every character. " Using count=1 causes text before the 'start' argument to be considered. function! GetMatches(line1, line2, pattern) let hits = [] for line in range(a:line1, a:line2) let text = getline(line) let from = 0 while 1 let next = match(text, a:pattern, from, 1) if next < 0 break endif let from = matchend(text, a:pattern, from, 1) if from > next call add(hits, strpart(text, next, from - next)) else let char = matchstr(text, '.', next) if empty(char) break endif let from = next + strlen(char) endif endwhile endfor return hits endfunction See also *Redirect g search output to redirect g// output to a new window or a file *Filter buffer on a search result to create a scratch buffer with matching lines *Find in files within Vim for a clickable list of search hits *Folding with Regular Expression to fold away non-matching lines Related plugins * provides a toolbox of commands to copy all (or only unique first) search matches / matches of a passed pattern / entire lines matching, to a register, or directly :put them into the buffer. * provides a :rangeYankituteregister/{pattern}/string/flags/join command. Comments Just after my last major update to this tip (a week ago), I found a note pointing to a vim_use thread where ZyX showed a simple yet brilliant technique for handling multiline matches. Testing shows that the posted code is not entirely correct as it misses multiline matches that occur in the same line after a short match. However, I have done extensive modifications and the resulting script (now in this tip) is sensational. JohnBeckett 03:31, May 3, 2012 (UTC)